package com.aletto.demo_android_web3j.utils;

import com.aletto.demo_android_web3j.C;

import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.FunctionReturnDecoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.abi.datatypes.generated.Uint8;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.Request;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.EthCall;
import org.web3j.protocol.core.methods.response.EthEstimateGas;
import org.web3j.protocol.core.methods.response.EthGasPrice;
import org.web3j.protocol.core.methods.response.EthGetBalance;
import org.web3j.protocol.core.methods.response.EthGetTransactionCount;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.tx.ChainId;
import org.web3j.utils.Convert;
import org.web3j.utils.Numeric;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;

public class ERC20Utils {

    public static EthSendTransaction buy(
            Web3j web3j,
            BigDecimal price,
            Long orderId,
            int number,
            int goodsType,
            Credentials credentials,
            String sellerAddress,
            String contractAddress) throws IOException {
        Function function = new Function(
                "buy",
                Arrays.asList(
                        new Uint256(orderId), // 购买的商品编号
                        new Uint256(Convert.toWei(price, Convert.Unit.ETHER).toBigInteger()), // 商品价格
                        new Uint256(BigInteger.valueOf(number)), // 购买的数量
                        new Uint8(BigInteger.valueOf(goodsType)), // 购买的商品类型
                        new Address(sellerAddress)), // 卖家地址
                Collections.emptyList());

        byte chainId = BigInteger.valueOf(97).byteValue();

        String data = FunctionEncoder.encode(function);

        // nonce
        EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount(
                credentials.getAddress(), DefaultBlockParameterName.LATEST).send();
        BigInteger nonce = ethGetTransactionCount.getTransactionCount();

        // gas
        EthGasPrice ethGasPrice = web3j.ethGasPrice().send();
        BigInteger gasEstimation = ethGasPrice.getGasPrice();

        // gas limit
        Transaction transaction = Transaction.createEthCallTransaction(credentials.getAddress(), contractAddress, data);
        EthEstimateGas ethEstimateGas = web3j.ethEstimateGas(transaction).send();
        BigInteger amountUsed = ethEstimateGas.getAmountUsed();
        BigInteger gasLimit = BigDecimal.valueOf(amountUsed.longValue()).multiply(new BigDecimal("2")).toBigInteger();

        RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasEstimation, gasLimit, contractAddress, data);
        byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials);

        return web3j.ethSendRawTransaction(Numeric.toHexString(signedMessage)).send();
    }

    /**
     * 查询余额
     *
     * @param who 查询地址
     */
    public static BigDecimal balance(Web3j web3j, String who) throws IOException {
        EthGetBalance ethGetBalance = web3j.ethGetBalance(who, DefaultBlockParameterName.LATEST).send();
        BigInteger balanceWei = ethGetBalance.getBalance();
        return Convert.fromWei(balanceWei.toString(), Convert.Unit.ETHER);
    }

    /**
     * 查询ERC20余额
     *
     * @param credentials     查询地址
     * @param contractAddress 合约地址
     */
    public static BigDecimal balance(Web3j web3j, Credentials credentials, String contractAddress) throws Exception {
        Function function = new Function(
                "balanceOf",
                Collections.singletonList(new Address(credentials.getAddress())),
                Collections.singletonList(new TypeReference<Uint256>() {
                    // 泛型不可省略
                })
        );

        Request<?, EthCall> ethCallRequest = web3j.ethCall(
                Transaction.createEthCallTransaction(
                        credentials.getAddress(),
                        contractAddress,
                        FunctionEncoder.encode(function)
                ),
                DefaultBlockParameterName.LATEST
        );

        EthCall ethCall = ethCallRequest.send();
        List<Type> decodedResponse = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());

        Uint256 allowance = (Uint256) decodedResponse.get(0);
        return Convert.fromWei(allowance.getValue().toString(), Convert.Unit.ETHER);
    }

    /**
     * 查看授权额
     *
     * @param contractAddress 合约地址
     * @param owner           所有者钱包地址
     * @param spender         授权地址
     */
    public static BigDecimal allowance(Web3j web3j, String contractAddress, String owner, String spender) throws IOException {
        Function function = new Function(
                "allowance",
                Arrays.asList(new Address(owner), new Address(spender)),
                Collections.singletonList(new TypeReference<Uint256>() {
                    // 泛型不可省略
                })
        );

        // 调用智能合约的allowance方法
        Request<?, EthCall> ethCallRequest = web3j.ethCall(
                Transaction.createEthCallTransaction(
                        owner,
                        contractAddress,
                        FunctionEncoder.encode(function)
                ),
                DefaultBlockParameterName.LATEST
        );

        EthCall ethCall = ethCallRequest.send();
        List<Type> decodedResponse = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());

        Uint256 allowance = (Uint256) decodedResponse.get(0);
        return Convert.fromWei(allowance.getValue().toString(), Convert.Unit.ETHER);
    }


    /**
     * 授权
     *
     * @param chainId         链ID
     * @param credentials     钥匙
     * @param spenderAddress  地址
     * @param contractAddress 地址
     * @param value           值
     */
    public static EthSendTransaction approve(
            Web3j web3j,
            byte chainId,
            Credentials credentials,
            String spenderAddress,
            String contractAddress,
            BigInteger value) throws IOException {

        Function function = new Function(
                "approve",
                Arrays.asList(new Address(spenderAddress), new Uint256(value)),
                Collections.emptyList()
        );

        String approveData = FunctionEncoder.encode(function);

        EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount(credentials.getAddress(), DefaultBlockParameterName.LATEST).send();
        Transaction transaction = Transaction.createEthCallTransaction(credentials.getAddress(), contractAddress, approveData);
        EthEstimateGas ethEstimateGas = web3j.ethEstimateGas(transaction).send();
        EthGasPrice ethGasPrice = web3j.ethGasPrice().send();

        BigInteger nonce = ethGetTransactionCount.getTransactionCount();
        BigInteger gasEstimation = ethGasPrice.getGasPrice();
        // TODO: gas limit 为什么这样设置？
        BigInteger gasLimit = BigDecimal.valueOf(ethEstimateGas.getAmountUsed().longValue()).multiply(new BigDecimal("3")).toBigInteger();

        RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasEstimation, gasLimit, contractAddress, approveData);
        byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials);

        return web3j.ethSendRawTransaction(Numeric.toHexString(signedMessage)).send();
    }


    /**
     * 离线签名eth
     *
     * @param to    转账的钱包地址
     * @param nonce 获取到的交易次数
     * @param value 转账的值
     */
    public static String signedTransaction(
            String privateKey,
            String to,
            BigInteger nonce,
            BigInteger gasPrice,
            BigInteger gasLimit,
            String value) {

        // 把十进制的转换成ETH的Wei, 1ETH = 10^18 Wei
        BigDecimal realValue = Convert.toWei(value, Convert.Unit.ETHER);
        RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
                nonce,
                gasPrice,
                gasLimit,
                to,
                realValue.toBigIntegerExact()
        );

        // 手续费 = (gasPrice * gasLimit ) / 10^18 ether
        Credentials credentials = Credentials.create(privateKey);

        // 使用TransactionEncoder对RawTransaction进行签名操作
//        byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, ChainId.MAINNET, credentials);
        byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, ChainId.ROPSTEN, credentials);

        // 转换成0x开头的字符串
        return Numeric.toHexString(signedMessage);
    }


    /**
     * 离线签名eth
     *
     * @param contractAddress 合约地址
     * @param to              转账的钱包地址
     * @param nonce           获取到的交易次数
     * @param amount          转账的值
     */
    public static String signedContractTransaction(
            String privateKey,      // 私钥
            String contractAddress, // 合约地址
            String to,              // 对方地址
            BigInteger nonce,       // 交易次数
            BigInteger gasPrice,
            BigInteger gasLimit,
            double amount,
            double decimal
    ) {

        // 因为每个代币可以规定自己的小数位, 所以实际的转账值 = 数值 * 10^小数位
        BigDecimal realValue = BigDecimal.valueOf(amount * Math.pow(10.0, decimal));

        // 0xa9059cbb代表某个代币的转账方法hex(transfer) + 对方的转账地址hex + 转账的值的hex
        String data = "0xa9059cbb"
                + Numeric.toHexStringNoPrefixZeroPadded(Numeric.toBigInt(to), 64)
                + Numeric.toHexStringNoPrefixZeroPadded(realValue.toBigInteger(), 64);

        // 合成
        RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, contractAddress, data);

        // 手续费 = (gasPrice * gasLimit ) / 10^18 ether
        Credentials credentials = Credentials.create(privateKey);

        // 使用TransactionEncoder对RawTransaction进行签名操作
        byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, ChainId.ROPSTEN, credentials);

        // 转换成0x开头的字符串
        return Numeric.toHexString(signedMessage);
    }

}
